#include "ecs_render_sys.hpp"
#include "ecs_sys_impl_include.hpp"

namespace njm
{
    // ecs render-system
    EcsRenderSystem::EcsRenderSystem()
    {
    }
    EcsRenderSystem::~EcsRenderSystem()
    {
    }
    void EcsRenderSystem::Update(float deltaTime, Coordinator &coordinator)
    {
        std::cout << "EcsRenderSystem update" << std::endl;
    }

    // true render-system
    RenderSystem::RenderSystem(
        NjmDevice &device, VkRenderPass renderPass, VkDescriptorSetLayout globalSetLayout,
        std::string vertShaderPath, std::string fragShaderPath)
        : njmDevice{device}, vertShaderPath{vertShaderPath}, fragShaderPath{fragShaderPath}
    {
        createPipelineLayout(globalSetLayout);
        createPipeline(renderPass);
    }
    RenderSystem::~RenderSystem()
    {
        vkDestroyPipelineLayout(njmDevice.device(), pipelineLayout, nullptr);
    }

    void RenderSystem::createPipelineLayout(VkDescriptorSetLayout globalSetLayout)
    {
        VkPushConstantRange pcRange{};
        pcRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
        pcRange.offset = 0;
        pcRange.size = sizeof(ECSPushConstantData);

        std::vector<VkDescriptorSetLayout> setLayouts{globalSetLayout};

        VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
        pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
        pipelineLayoutInfo.setLayoutCount = static_cast<uint32_t>(setLayouts.size());
        pipelineLayoutInfo.pSetLayouts = setLayouts.data();
        pipelineLayoutInfo.pushConstantRangeCount = 1;
        pipelineLayoutInfo.pPushConstantRanges = &pcRange;

        if (vkCreatePipelineLayout(njmDevice.device(), &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS)
        {
            throw std::runtime_error("RenderSystem::createPipelineLayout : failed to create pipelineLayout");
        }
    }

    void RenderSystem::createPipeline(VkRenderPass renderPass)
    {
        assert(pipelineLayout != nullptr && "RenderSystem::createPipeline : pipelineLayout is nullptr");
        assert(renderPass != nullptr && "RenderSystem::createPipeline : renderPass is nullptr");
        assert(vertShaderPath != "" && "RenderSystem::createPipeline : vertShaderPath is empty");
        assert(fragShaderPath != "" && "RenderSystem::createPipeline : fragShaderPath is empty");

        PipelineConfigInfo pipelineConfig{};
        NjmPipeline::defaultPipelineConfigInfo(pipelineConfig);
        pipelineConfig.renderPass = renderPass;
        pipelineConfig.pipelineLayout = pipelineLayout;

        njmPipeline = std::make_unique<NjmPipeline>(
            njmDevice,
            vertShaderPath, fragShaderPath,
            pipelineConfig);
    }

    void RenderSystem::render(ECSFrameInfo &frameInfo)
    {
        std::cout << "true RenderSystem rendering" << std::endl;
        njmPipeline->bind(frameInfo.commandBuffer);
        vkCmdBindDescriptorSets(frameInfo.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
                                pipelineLayout, 0, 1, &frameInfo.globalDescriptorSet, 0, nullptr);
        auto &entities = (frameInfo.coordinator->GetSystem<EcsRenderSystem>())->mEntities;
        for (auto &&e : entities)
        {
            auto &transform = frameInfo.coordinator->GetComponent<Transform>(e);
            ECSPushConstantData push{};
            push.modelMat = transform.mat4();
            push.normalMat = transform.normalMatrix();
            vkCmdPushConstants(frameInfo.commandBuffer, pipelineLayout,
                               VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
                               0, sizeof(ECSPushConstantData), &push);
            auto &modelId = frameInfo.coordinator->GetComponent<Model>(e).modelId;
            auto &model = frameInfo.models.at(modelId);
            model->bind(frameInfo.commandBuffer);
            model->draw(frameInfo.commandBuffer);
        }
    }

}
